package org.epsi.blokus.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.epsi.blokus.commun.Message;
import org.epsi.blokus.commun.Message.TTypeMessage;

/**
 * Serveur principal
 * @author julien.lafont
 */
public class Server extends Observable implements Observer {

    // ---- Un seul server en singleton ------------------
    private static Server _instance = null;

    public static Server getInstance()
    {
        if (_instance == null) _instance = new Server();
        return _instance;
    }

    private Server()
    {
        // Empèche la création public d'une instance
    }

    // -------------- Fin Singleton ------------------------


    private ServerSocket ss = null;
    private ExecutorService es = null;
    private SalonManager salonManager = null;

    
    public void start(int port)
    {
       
        notifyGui("Server > Lancement");
        
        try {

            // Serveur de socket
            ss = new ServerSocket(port);

            // Pool d'exécution des thread
            es = Executors.newCachedThreadPool();

            // Nouveau gestionnaire des salons
            salonManager = new SalonManager();

            while(true)
            {
                // On attend la connexion d'un client
                Socket sock = ss.accept();
                
                notifyGui("Server > Nouveau client");

                // Nouveau client
                ServerClient sc = new ServerClient(sock);

                // Le serveur observe ce client
                sc.addObserver(this);

                // Exécute le client dans un thread
                es.execute(sc);

            }

        } catch (IOException ex) {
           notifyGui("Server > Erreur entrain la fermeture du serveur");
        } finally {
           forcerFermetureServerSocket();
        }
    }

    
    /**
     * Forcer la fermeture du salon
     */
    private void forcerFermetureServerSocket()
    {
         try {
            es.shutdown();
            ss.close();
        }
        catch(IOException ex) {}
    }


    /**
     * Réception d'un message en provenance d'un client en relation avec le serveur
     * @param o : Objet observable (le client)
     * @param arg : Message transmis
     */
    public void update(Observable o, Object arg) {
        ServerClient source = (ServerClient)o;

        Message m = (Message)arg;

        source.envoyerMessage(m);

        // On traite chaque type de message
        switch(m.getTypeMessage())
        {

            // Connexion initiale d'un client
            case connexion_client:

                notifyGui("Connexion d'un client", source);
                
                salonManager.nouveauClient(source);
                notifyGuiNbClients();
                
            break;

            // Création d'un nouveau salon
            case creation_salon:

                try {
                     Integer ID = salonManager.creerSalon(source,(String)m.getObject("NOMSALON"));
                    
                     notifyGui("Création du salon "+m.getObject("NOMSALON")+" réussie", source);
                     notifyGuiNbSalons();
                } catch(Exception e) { 
                    source.envoyerMessage(new Message(TTypeMessage.creation_salon_err));
                    notifyGui("Echec dans la création d'un salon", source);
                }

            break;

            // Un maitre de salon ferme celui-ci
            case fermeture_salon:

                this.deleteObserver(source.getSalon());
                salonManager.fermerSalon(source);

                notifyGui("Fermeture du salon géré par Client "+source.getNumero()+" réussie");
                notifyGuiNbSalons();
              
            break;

            // Un client veut sortir d'un salon
            case sortir_salon:
                try {
                    Integer ID2 = (Integer)m.getObject("IDSALON");
                    salonManager.quitterSalon(source);
                    
                } catch (Exception e){
                    source.envoyerMessage(new Message(TTypeMessage.sortir_salon_err)); // On peut penser renvoyer la raison de l'erreur
                    notifyGui("Echec dans la sortie du salon", source);
                }
            break;

            // Un client veut entrer dans un salon
            case entrer_salon:
                try {
                    Integer ID2 = (Integer)m.getObject("IDSALON");
                    salonManager.rejoindreSalon(source, ID2);
                   
                    notifyGui("Inscription dans le salon "+ID2+" réussie", source);
                    
                } catch (Exception e) { // TODO Là on peut gérer des exceptions personnalisées
                    source.envoyerMessage(new Message(TTypeMessage.entrer_salon_err).addObject("ERREUR", e.getMessage()));
                    notifyGui("Echec dans l'inscription à un salon", source);
                }

            break;

            // Déconnexion d'un joueur, qui est ou non dans un salon
            case deconnexion_client:

                try {
                    
                    salonManager.retirerClient(source);
                    notifyGui("Déconnexion du client", source);

                } catch (Exception e) { 
                    source.envoyerMessage(new Message(TTypeMessage.deconnexion_client_ack).addObject("RES", "Erreur !"));
                    notifyGui("Déconnexion du client avec une erreur", source);
                } finally {
                    notifyGuiNbClients();
                    notifyGuiNbSalons();
                }

            break;

            // Message chat sur le salon
            case chat_salon_poster:

                Message reponse = new Message(TTypeMessage.chat_salon_recevoir)
                        .addObject("Message", (String)m.getObject("Message"))
                        .addObject("Source", "Client "+source.getNumero());
                this.salonManager.envoyerMessageTousLesJoueursSauf(reponse, source);
                
            break;


            case lancer_partie:
                Salon salon = source.getSalon();
                salon.lancerPartie();

                notifyGui("Lancement de la partie sur le salon "+salon.getNom());
            break;

            // Combien de joueurs dans ce salon
            case requete_nb_joueurs:
                Integer idSalon = Integer.parseInt((String) m.getObject("IDSALON"));
                int nbJoueurs = salonManager.getNbJoueursDansSalon(idSalon);

                source.envoyerMessage(new Message(TTypeMessage.reponse_nb_joueurs).addObject("NB", nbJoueurs));
                
            case indefini:
                notifyGui("Réception d'un message indéfini", source);
            break;
        }

    }

    // --------------------- Envoie des messages au GUI ----------------------

    /**
     * Notifier le GUI d'un message texte
     * @param message
     */
    private void notifyGui(String txt)
    {
        this.notifyGui(new MessageInterne().addObject("LINE", txt));
    }

    /**
     * Notifier le GUI d'un message texte relié à un client
     * @param message
     * @param src
     */
    private void notifyGui(String txt, ServerClient src)
    {
        this.notifyGui(new MessageInterne().addObject("LINE", "Client "+src.getNumero()+" > "+txt));
    }

    /**
     * Notifie le GUI d'un changement sur le nombre de clients connectés
     */
    private void notifyGuiNbClients()
    {
        this.notifyGui(
                new MessageInterne(
                    MessageInterne.TTypeMessageInterne.maj_nb_joueurs)
                    .addObject("NB", ""+this.salonManager.getNbJoueurs()
                 )
        );
    }

    /**
     * Notifie le GUI d'un changement sur le nombre de salon lancés
     */
    private void notifyGuiNbSalons()
    {
        this.notifyGui(
                new MessageInterne(
                    MessageInterne.TTypeMessageInterne.maj_nb_salons)
                    .addObject("NB", ""+this.salonManager.getNbSalons()
                )
         );
    }

    /**
     * Envoie un message de type "Message Interne" au GUI
     * @param message
     */
    private void notifyGui(MessageInterne message) {
        setChanged();
        notifyObservers(message);
        clearChanged();
    }

}
